home *** CD-ROM | disk | FTP | other *** search
- /*
- File: SampleFileTranslationExt.c
-
- Contains: Sample code shell for writing a File Translation Extension.
-
- Written by: Nick Kledzik & Dylan Ashe
-
- Copyright: © 1991-1992 by Apple Computer, Inc., all rights reserved.
-
- */
-
-
-
- #include <Resources.h>
- #include "Components.h"
- #include "Errors.h"
- #include "Files.h"
- #include "TranslationExtensions.h"
-
-
- /*
- Notes:
-
- Errors:
- All routines return OSErr's. If they succeed they should return noErr.
- The component manager requires all routines to return a ComponentResult
- which is a 32-bit long to simplify dispatching. If your code returns an OSErr
- the compiler will automatically promote it to a long (ComponentResult).
-
- Globals:
- Your routines can not use globals. There really is no reason they should
- need to use globals. The three routines are separate and self contained.
- If you persist and try to use PC relative globals, be warned that the
- ComponentMgr may purge and reload your code resource. Therefore all
- PC relative globals must be preinitailized at compile time (not load time).
- Also, the ComponentMgr routine SetComponentInstanceStorage can not be
- used, because InstanceStorage is already used by the TranslationMgr.
-
- Memory allocation:
- When one of your three routines is called, the default heap is the System
- heap. If you need to load any other code resources or allocate any memory
- it is up to you to choose the best heap. The System heap was choosen as
- the default because many applications (including the Finder) have their
- own memory management scheme that only leaves minimal free space in the
- application heap.
-
- Resources:
- If you need to access resources from your Translation Extension file, you
- will need to use OpenComponentResFile() and CloseComponentResFile().
- The open routine requires the ComponentInstance parameter supplied to
- your routine.
-
-
- */
-
-
-
- /***********************************************************************************************
- *
- * The purpose of DoGetFileTranslationList is to tell the TranslationMgr what file formats
- * this extension can translate between.
- *
- * You are given the list that you built for the TranslationMgr last time, or if this is
- * the first time you've been called, the list if empty (groupCount = 0, modDate = 0).
- *
- * You need to figure out if the file formats we can translate between has changed. For
- * example, the user has installed more translators. One way to check this is to compare
- * the modDate of the list with the modDate of the file/folder of the translators.
- * If it has not changed, you just return without changing the list.
- *
- *
- * Catalog information about your available translators
- *
- * Divide the translators into groups. Within each group, any of the 'src' formats
- * can used with an of the 'dst' formats (any reader can be connected to any writer).
- *
- * Resize the translationList handle to hold all the information about each group.
- * Fill in the translationList.
- *
- * Use the modDate field as a "version" of the list. When you are called again, you can
- * use it to quickly check if the list needs to be rebuilt. The TranslationMgr checks the
- * modDate of the list before and after call you DoGetFileTranslationList. If the modDate
- * has changed, the TranslationMgr updates its internal tables of translation info and saves
- * the new list to disk for use next time that DoGetFileTranslationList is called.
- *
- * The groupCount field is the number of groups in the list.
- * Each group consists of a list of 'src' and 'dst' FileTypeSpec's. Each FileTypeSpec
- * contains the FileType of the file format that will be read or written, a 32-bit hint,
- * translation attributes, and the canonical HFS type and creator.
- * This hint is for use by your File Translation Extension. It is intended to be used to
- * quickly locate the required translator.
- *
- * An example translationList that converts from WriteNow™ to MacWrite™ 5.0 documents would
- * look list this:
- *
- * 0xA5BC1234 ; modDate
- * 1 ; only 1 group
- * 20 ; size of each entry = sizeof(FileTypeSpec)
- * 1 ; 1 source type
- * 'nX^d' ; WriteNow FileType
- * 0 ; hint for WriteNow FileType (0=unused)
- * 0 ; no attributes
- * 'nX^d' ; HFS type of WriteNow documents
- * 'nX^n' ; HFS creator of WriteNow documents
- * 1 ; 1 destination type
- * 20 ; size of each entry = sizeof(FileTypeSpec)
- * 'WORD' ; MacWrite 5.0 FileType
- * 0 ; hint for MacWrite 5.0 FileType (0=unused)
- * 0 ; no attributes
- * 'WORD' ; HFS type of MacWrite 5.0 documents
- * 'MACA' ; HFS creator of MacWrite 5.0 documents
- *
- *
- * If you return an error, the TranslationMgr will ignore your translation list. This will
- * effectively disable your extension from being called. When the machine is restarted,
- * your DoGetFileTranslationList will get called again.
- *
- ************************************************************************************************/
- pascal ComponentResult DoGetFileTranslationList(ComponentInstance self,
- FileTranslationListHandle translationList)
- {
- #pragma unused(self)
- FileTranslationListPtr pList = *translationList;
-
- // the data never changes, so just check for initial empty list
- if ( (pList->groupCount != 1) || (pList->modDate != 0xA58FA300) )
- {
- long* pLong;
-
- pList->modDate = 0xA58FA300; // about now
- pList->groupCount = 1;
-
- SetHandleSize((Handle)translationList, sizeof(FileTranslationList) + 2 * (2*sizeof(FileTypeSpec)+8));
- if (MemError() != noErr)
- return MemError();
-
- pLong = (long*)((Ptr)*translationList + sizeof(FileTranslationList));
-
- *pLong++ = 2; // group1FromCount
- *pLong++ = sizeof(FileTypeSpec); // group1FromEntrySize
- *pLong++ = 'TEXT'; // group1FromTypes[1].format
- *pLong++ = 0; // group1FromTypes[1].hint
- *pLong++ = 0; // group1FromTypes[1].flags
- *pLong++ = 'TEXT'; // group1FromTypes[1].catInfoType
- *pLong++ = 'ttxt'; // group1FromTypes[1].catInfoCreator
- *pLong++ = 'PIGL'; // group1FromTypes[2].format
- *pLong++ = 0; // group1FromTypes[2].hint
- *pLong++ = 0; // group1FromTypes[2].flags
- *pLong++ = 'PIGL'; // group1FromTypes[2].catInfoType
- *pLong++ = 'PIGH'; // group1FromTypes[2].catInfoCreator
-
- *pLong++ = 2; // group1ToCount
- *pLong++ = sizeof(FileTypeSpec); // group1ToEntrySize
- *pLong++ = 'TEXT'; // group1ToTypes[1].format
- *pLong++ = 0; // group1ToTypes[1].hint
- *pLong++ = 0; // group1ToTypes[1].flags
- *pLong++ = 'TEXT'; // group1ToTypes[1].catInfoType
- *pLong++ = 'ttxt'; // group1ToTypes[1].catInfoCreator
- *pLong++ = 'PIGL'; // group1ToTypes[2].format
- *pLong++ = 0; // group1ToTypes[2].hint
- *pLong++ = 0; // group1ToTypes[2].flags
- *pLong++ = 'PIGL'; // group1ToTypes[2].catInfoType
- *pLong++ = 'PIGH'; // group1ToTypes[2].catInfoCreator
- };
-
- return noErr;
- };
-
-
-
-
-
- Boolean IsPigLatin(char* word)
- {
- // go to end
- while (*word++);
-
- // back up to last two chars in word
- word -= 3;
-
- // to be pig latin last two chars of word must be 'ay'
- return ( (*word++ == 'a') && (*word == 'y') );
- };
-
-
- /***********************************************************************************************
- *
- * The purpose of DoIdentifyFile is to look at the data in a file and determine its FileType.
- *
- * All installed File Translation Extensions are called to identify a file.
- * The value of docKind is initially set to NIL and each extension is called in succession.
- * Each has a chance to set docKind. To handle the case when a document format can be a
- * “subclasses” of another, an extension should only set docKind if docKind is already set
- * to a known “superclass” or NIL. For example, because RTF is just ASCII characters “RTF”
- * is a subclass of “Plain Text”. This means an RTF document could be identified as ‘RTF ’
- * or ‘TEXT’. Using the above rule, the extension could overwrite ‘TEXT’ with ‘RTF ’, but
- * not the other way around.
- *
- * return noTypeErr if you do not recognize the document format.
- *
- ************************************************************************************************/
- pascal ComponentResult DoIdentifyFile(ComponentInstance self, const FSSpec* theDoc, FileType* docKind)
- {
- #pragma unused(self)
- FInfo fndrInfo;
- OSErr err;
- short srcRefNum = -1;
- char fileBuffer[256];
- char wordBuffer[256];
- char* nextCharInFileBuffer;
- char* nextCharInWordBuffer;
- char c;
- Size count;
-
- err = FSpGetFInfo(theDoc, &fndrInfo);
-
- if ( (err != noErr) || ((fndrInfo.fdType != 'TEXT')&&(fndrInfo.fdType != 'PIGL')) )
- {
- return noTypeErr;
- };
-
- // assumme it is a text file, then figure out what kind it really is
- *docKind = 'TEXT';
-
- // open source
- err = FSpOpenDF(theDoc, fsRdPerm, &srcRefNum);
- if (err != noErr)
- return noTypeErr;
-
- // get first chunk of data from file
- count = 256;
- err = FSRead(srcRefNum, &count, &fileBuffer[0]);
- FSClose(srcRefNum);
- if ( (err != noErr) && (err != eofErr) )
- return noTypeErr;
-
- nextCharInWordBuffer = &wordBuffer[0];
- nextCharInFileBuffer = &fileBuffer[0];
- while ( count-- > 0 )
- {
- c = *nextCharInFileBuffer++;
- if ( ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) )
- {
- *nextCharInWordBuffer++ = c;
- }
- else
- {
- // found something that cause a word break
- if ( nextCharInWordBuffer != &wordBuffer[0] ) // is there something in the buffer?
- {
- // see if it is pig latin
- *nextCharInWordBuffer = '\0';
- if ( IsPigLatin(wordBuffer) == false )
- return noErr; // if not, must be FileType TEXT
-
- // reset for next word
- nextCharInWordBuffer = &wordBuffer[0];
-
- };
- };
- };
-
- // every word in header was pig latin, so must be pig latin
- *docKind = 'PIGL';
- return noErr;
-
- };
-
-
-
- Boolean GetChar(short srcRefNum, char* c)
- {
- long count = 1;
-
- return ( FSRead(srcRefNum, &count, c) == noErr );
- };
-
-
- void PutChar(short dstRefNum, char c)
- {
- long count = 1;
-
- FSWrite(dstRefNum, &count, &c);
- };
-
-
- Boolean IsConsonant(char c)
- {
- switch (c)
- {
- case 'a':
- case 'A':
- case 'e':
- case 'E':
- case 'i':
- case 'I':
- case 'o':
- case 'O':
- case 'u':
- case 'U':
- return false;
- default:
- return true;
- };
- };
-
-
-
- void ConvertToPigLatin(char* s)
- {
- char* sEnd;
- short sLength;
- Boolean capitalized;
-
- // remember if word is capitalized
- capitalized = false;
- if ( (*s >= 'A') && (*s <= 'Z') )
- {
- capitalized = true;
- *s += ('a' - 'A'); // make it lowercase
- };
-
- // find end of string
- for (sEnd = s, sLength = 1; *sEnd != '\0'; sEnd++,sLength++);
-
- // if starts with consonants, move then to the end
- if ( IsConsonant(*s) )
- {
- char* t = s;
- short i = 0;
-
- // move consonants to end
- while ( IsConsonant(*t) && (i<sLength) )
- {
- *sEnd++ = *t++;
- i++;
- };
-
- *sEnd = '\0';
- BlockMove(t, s, sLength); // move string to begin at start of buffer
- sEnd -= i; // adjust end pointer back
- };
-
- // if word was capitalized, make new word also
- if (capitalized)
- *s -= ('a' - 'A'); // make it uppercase
-
- // append "ay"
- *sEnd++ = 'a';
- *sEnd++ = 'y';
- *sEnd++ = '\0';
- };
-
-
-
- void ConvertFromPigLatin(char* s)
- {
- char* sEnd;
- short sLength;
- Boolean capitalized;
-
- // remember if word is capitalized
- capitalized = false;
- if ( (*s >= 'A') && (*s <= 'Z') )
- {
- capitalized = true;
- *s += ('a' - 'A'); // make first letter lowercase
- };
-
- // find end of string
- for (sEnd = s, sLength = 1; *sEnd != '\0'; sEnd++,sLength++);
-
- // back up two to remove 'ay'
- sEnd -= 2;
- sLength -= 2;
- *sEnd = '\0';
-
- // for short words do nothing
- if ( (sLength > 3) || ((sLength == 3) && (s[0]=='e')) )
- {
- // move last char to start
- BlockMove(s, &s[1], sLength); // need to shift rest of word
- s[0] = s[sLength-1];
- s[sLength-1] = '\0';
- };
-
- // special case 'th'
- if ( (s[0] == 'h') && (s[sLength-2] == 't') )
- {
- BlockMove(s, &s[1], sLength); // need to shift rest of word
- s[0] = s[sLength-1];
- s[sLength-1] = '\0';
- };
-
- // if word was capitalized, make new word also
- if (capitalized)
- *s -= ('a' - 'A'); // make it uppercase
-
- };
-
-
- #define progressAdvertismentResID 150
-
- /***********************************************************************************************
- *
- * The purpose of DoTranslateFile is to translate a file from one format to a file of another format.
- *
- * progressRefNum is a refum passed to the progress call back routines.
- * srcDoc is the source document from which to read and translate.
- * srcType is the FileType of the source document as verified by DoIdentifyFile.
- * srcTypeHint is the hint that was paired with srcType in your FileTranslationList.
- * dstDoc is the destination document to which to write the translated file.
- * dstType is the desired FileType of the translated file.
- * dstTypeHint is the hint that was paired with dstType in your FileTranslationList.
- *
- * A note about 'hints'. They are only hints. Your extension must be able to operate without
- * them or if they are incorrect.
- *
- * You can use all the standard File Manager and Memory Manager routines to accomplish the translation.
- *
- * The first thing you should do is call SetTranslationAdvertisement. It will draw the progress
- * dialog window. The PicHandle parameter is a handle to the PICT that will be drawn in
- * in the top of the progress dialog. If it is NIL, no advertisment will be drawn, and the
- * dialog will be shrunk down to eliminate the blank space. Note, this shrinking will also
- * happen if you call UpdateTranslationProgress before SetTranslationAdvertisement. The
- * TranslationMgr will DisposeHandle the PICT when the translation is done. Therefore,
- * the PICT should be a non-purgable, non-resource handle. If you store it as a resource
- * in your Translation Extension, use OpenComponentResFile and DetachResource.
- *
- * You must periodically call the progress "call back" routine UpdateTranslationProgress.
- * You pass to it a number from one to one hundred that represents the
- * percentage of the translation that has been completed.
- * The progress routine updates the progress status bar and handles the events a user can
- * do to cancel the translation. If the user does cancel, that fact is returned to you by the
- * progress routine. You should abort the translation, clean up anything you have allocated
- * and return userCancelErr.
- *
- ************************************************************************************************/
- pascal ComponentResult DoTranslateFile(ComponentInstance self, TranslationRefNum progressRefNum,
- const FSSpec* srcDoc, FileType srcType, long srcTypeHint,
- const FSSpec* dstDoc, FileType dstType, long dstTypeHint)
- {
- #pragma unused(srcType)
- #pragma unused(srcTypeHint)
- #pragma unused(dstType)
- #pragma unused(dstTypeHint)
- OSErr err;
- short srcRefNum = -1;
- short dstRefNum = -1;
- Size srcSize;
- Size curPos;
- Str255 wordBuffer;
- char* nextCharInBuffer;
- char c;
- Boolean canceled;
-
- // first thing to do is display progress dialog and show advertisment
- {
- Handle advert;
- short myResFile;
-
- myResFile = OpenComponentResFile((Component)self);
- if (myResFile != -1)
- {
- advert = GetResource('PICT', progressAdvertismentResID);
- DetachResource(advert);
- SetTranslationAdvertisement(progressRefNum, (PicHandle)advert);
- CloseComponentResFile(myResFile);
- };
- }
-
- // open source
- err = FSpOpenDF(srcDoc, fsRdPerm, &srcRefNum);
- if (err != noErr)
- goto abort;
- err = GetEOF(srcRefNum, &srcSize);
- if (err != noErr)
- goto abort;
-
- // open destination
- err = FSpOpenDF(dstDoc, fsRdWrPerm, &dstRefNum);
- if (err != noErr)
- goto abort;
-
- nextCharInBuffer = &wordBuffer[0];
- curPos = 0;
- while ( GetChar(srcRefNum, &c) )
- {
- curPos++;
- if ( ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) )
- {
- *nextCharInBuffer++ = c;
- }
- else
- {
- // is there something in the buffer?
- if ( nextCharInBuffer != &wordBuffer[0] )
- {
- // convert it to pig latin
- *nextCharInBuffer = '\0';
- if ( dstType == 'PIGL' )
- ConvertToPigLatin(wordBuffer);
- else
- ConvertFromPigLatin(wordBuffer);
-
- // flush buffer
- nextCharInBuffer = wordBuffer;
- while ( *nextCharInBuffer )
- PutChar(dstRefNum, *nextCharInBuffer++);
-
- // reset for next word
- nextCharInBuffer = &wordBuffer[0];
-
- // update progress dialog
- UpdateTranslationProgress(progressRefNum, curPos*100/srcSize, &canceled);
- if (canceled)
- {
- err = userCanceledErr;
- goto abort;
- };
- };
- // write char that caused work break
- PutChar(dstRefNum,c);
- };
- };
- // is there something left in the buffer?
- if ( nextCharInBuffer != &wordBuffer[0] )
- {
- // convert it to pig latin
- *nextCharInBuffer = '\0';
- if ( dstType == 'PIGL' )
- ConvertToPigLatin(wordBuffer);
- else
- ConvertFromPigLatin(wordBuffer);
-
- // flush buffer
- nextCharInBuffer = wordBuffer;
- while ( *nextCharInBuffer )
- PutChar(dstRefNum, *nextCharInBuffer++);
-
- // reset for next word
- nextCharInBuffer = &wordBuffer[0];
-
- // update progress dialog
- UpdateTranslationProgress(progressRefNum, 100, &canceled);
- if (canceled)
- {
- err = userCanceledErr;
- goto abort;
- };
- };
-
- abort:
- if (srcRefNum != -1) FSClose(srcRefNum);
- if (dstRefNum != -1) FSClose(dstRefNum);
-
- return err;
- };
-
-
-